package tcln.sors.client.core.mvc.modelmapping;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;

import tcln.commons.assertion.Assert;
import tcln.commons.beans.BeanUtils;
import tcln.commons.exception.BaseRuntimeException;
import tcln.sors.client.core.mvc.ListModelData;
import tcln.sors.client.core.mvc.ModelData;

/**
 * Default implementation of a model data mapper which uses 'shallow copy' method map from object to model class. All
 * 'read' methods (ie. <code>getXXX</code> or <code>isXXX</code> methods) from source object will be used to get all
 * properties' values and then set them sequentially to target model. The visibility of these read methods are ignored,
 * which means that all kinds of <code>getXXX</code> or <code>isXXX</code> methods will be considered as candidates for
 * mapping.
 * 
 * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
 */
public class DefaultModelDataMapper implements ModelDataMapper {

    /**
     * {@inheritDoc}
     */
    @Override
    public <M extends ModelData<?>> M map(Object obj, M targetModel) {
        Assert.ARGUMENTS.notNull(obj, "Source object must not be null");
        Assert.ARGUMENTS.notNull(targetModel, "Target model must not be null");

        Set<Method> readMethods = BeanUtils.collectAllReadMethods(obj);
        try {
            for (Method readMethod : readMethods) {
                readMethod.setAccessible(true);
                Object value = readMethod.invoke(obj);
                targetModel.set(BeanUtils.getPropertyName(readMethod), value);
            }
        } catch (Exception e) {
            throw new BaseRuntimeException(e);
        }

        return targetModel;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <M extends ModelData<?>> M map(Object obj, Class<M> targetModelClass) {
        Assert.ARGUMENTS.notNull(obj, "Source object must not be null");
        Assert.ARGUMENTS.notNull(targetModelClass, "Target model class must not be null");

        M targetModel = BeanUtils.newInstance(targetModelClass);
        targetModel = map(obj, targetModel);
        return targetModel;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <M extends ModelData<?>> ListModelData<M> map(List<?> objects, Class<M> targetChildModelClass) {
        ListModelData<M> listModelData = new ListModelData<M>(objects.size());
        for (Object object : objects) {
            M childModel = map(object, targetChildModelClass);
            listModelData.getChildModels().add(childModel);
        }
        return listModelData;
    }
}
